﻿using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
using PSIEpics;
using epics_server_remoting_class;
using JEpicsServer_driver;
using JtextEpicsInterface;
using System.Reflection;
using System.Threading;
using System.Reflection;

//a parent of the jtext_EpicsRecord<T>

namespace jtext_EpicsServer
{
    /// <summary>
    /// The Parent of all kind of record,
    ///   including array record and normal record enable you to treat them as the same thing.
    /// </summary>
    public  class JEpicsRecord:IJERecord
    {
        /// <summary>
        /// where  the record's EpicsRecord acturly kept
        /// </summary>
        protected  EpicsServer_Remoting ParentRemoteServer;  //this will work with the ESRC(EpicsServer_Remoting) instead of the JEpicsServer
       // JEpicsDriver MyEJDriver; //each record has its own JEDriver this handles the hardware job

        /// <summary>
        /// contains the plugin instance
        /// </summary>
        protected Hashtable myPlugIns;


        /// <summary>
        /// the PvName of this record
        /// </summary>
        protected  string MyPvName;

        /// <summary>
        /// for input ,the raw value from the hardware
        /// <para>for output ,the value from the pv </para>
        /// </summary>
        protected   object  ValA;   //the raw value of the record  get directly from hardware
        
        /// <summary>
        /// for input ,value that processed by plugIn and ready to put into PV
        /// <para>for output ,value that processed by plugIn and ready to set to the hardware </para>
        /// </summary>
        public  object ValB;  //the proccessed value  that go to the PV.VAL

        /// <summary>
        /// where the record is hosted
        /// </summary>
        protected JEpicsServer myParentServer;    //the parent JEServer


        /// <summary>
        /// is it a input or out put,ofr soft PV that has not driver we1recommand to use ouput
        /// </summary>
        public  bool IsInput;   //if it is a input redoce if not it's a output
        /// <summary>
        /// is the record  running ?:is the hardware ok for this record,
        /// </summary>
        protected bool IsRunning;   //

        /// <summary>
        /// the driver this record is gonna use
        /// </summary>
        public IJEDriver myDriver;  //

        /// <summary>
        /// the property of the record
        /// <para>all the information about this record</para>
        /// </summary>
        public Record_prop myRecordProp;   //the basic epics record property

        /// <summary>
        /// Occurs when the record flushes itself.
        /// <para>record itself can also trigger a event when flushed by itself the flush timer</para>
        /// </summary>
        public event FlushRecordHandler OnMyFlush;  //the record has a timer thread that will trigger this event ,when the record is self positive flush

        /// <summary>
        /// generating flush event 
        /// </summary>
        private Timer FlushTimer;   //is selft positive flush this genarate OnMySelf Flush event at a certain interval


        /// <summary>
        /// handles the flush event
        /// </summary>
        private TimerCallback FlushDelegate;    //timer will run this delegate

        /* 
        //public RecordDriverProp myDriverProp;  //contain the driver info     
        //protected IEJPlugIn myPlugIN;

        
        //public delegate object GetValFromHard(JEpicsRecord myEJrecord);   //the dalagate to get a raw value from the hardware
        //public GetValFromHard GetValHardHandler;

        //public delegate object SetValToHard(JEpicsRecord myEJrecord);   //the dalagate to set a raw value to the hardware
        ////if it's a output record youwill need this   
        //public  SetValToHard SetValHardHandler;


        //public delegate object RawToProcVal(JEpicsRecord  myEJrecord);       //proccess the raw value and get the ProcVal    
        ////you may always wants ProcVal=RawVal,you leave the  GetValHardHandler do the dirty work
        ////and the client do the fine work
        ////some times it may not like this
        //public RawToProcVal RawToProcHandler;   //in short R2P


        //public delegate object ProcToRawVal(JEpicsRecord myEJrecord); //if it's a output record youwill need this   
        //public ProcToRawVal ProcToRawHandler;   //inshort P2R
        */

        #region Full Flush
        /// <summary>
        /// Full flush a record.
        /// <para>For input:first get value from hardware,then put into ValA and ValB; then run plugin ,it may change Valb;then ValB is putinto PV ,over</para>
        /// <para>For output:first get value from PV,then put into ValA and ValB; then run plugin ,it may change Valb;then ValB is output to hardware ,over</para>
        /// </summary>
        public virtual void BigFlush()
        {
            //try
            //{
                this.Flush();
                this.callPlugin();
                this.Flush2();
            //}
            //catch
            //{
            //    throw new System.Exception("Driver or Plugin Error");
            //}
        }

        public virtual  void Flush()
        {
            //for input first hard->valA
            //for output first PV->vaLa
        }

        /// <summary>
        /// Calls the plugin.
        /// <para>each record may have many plugins,run them in order</para>
        /// </summary>
        public virtual  void callPlugin()
        {
            if (IsRunning == false)
                return;
            if (myRecordProp.PlugIns == null)
                return;
            foreach (string TPlugInName in myRecordProp.PlugIns)
            {
                if (myPlugIns.Contains(TPlugInName))   //if the server load the plugin
                {
                    ((IJEPlugIn)myPlugIns[TPlugInName]).RunPlugIn((IJERecord)this);
                }
            }
        }

        public virtual void Flush2()
        {
            //for input then valb->PV
            //for output then valB->hard
        }

        /// <summary>
        /// when it's time to flush this record ,this is called
        /// </summary>
        /// <param name="inPut">Not used</param>
        private void SelfFlush(object inPut)    //the argument is not used ,just rais event
        {
            if (OnMyFlush != null)
            {
                OnMyFlush();    //rais event
            }
        }

        
        #endregion

        /// <summary>
        /// Initializes a new instance of the <see cref="JEpicsRecord"/> class.
        /// </summary>
        /// <param name="myRServer">the remoting server that holds the epicsRecord.</param>
        /// <param name="my_RP">the record property.</param>
        public JEpicsRecord(EpicsServer_Remoting myRServer, Record_prop  my_RP)  //constructor
        {
            //the constructor
            //initialize all things for this record
            //it may(most likely) read xml file which contains the record info
            myPlugIns=new Hashtable();
            MyPvName = my_RP.pvName;
            ParentRemoteServer = myRServer;
            IsRunning = false;
            IsInput = my_RP.isInput;
            ValA = new object();
            ValB = new object();

        }

        #region ini
        /// <summary>
        ///initialze the driver,notice when  before the server started and record been added,the server ini the drivers for the first time as a whole device,,
        ///then after that you should ini the driver's each channel,so this is called
        /// </summary>
        public virtual void iniDriver()
        {
            //ini the driver,notice when the before the server started and record been added,the server ini the drivers for the first time as a whole device
            //then after that you should ini the driver's each channel
        }

        /// <summary>
        /// Initialze  the record,Initialize driver for this record like initialze the channel for this record.
        /// <para>This is a vertual funtion so the child of this woul override this</para>
        /// </summary>
        /// <returns></returns>
        public virtual bool IniRecord()
        {
            //after this  the record willbe running 
            //if not flushed by server it flush itself at his own rate create scan thread here
            //test code
            //ini the drivers
            iniPlugIns();
            if (IniRecordDriver() != true) 
            {
                return false;
            }
            return true;
        }

        /// <summary>
        /// Inis the plugins.create instance for each plugin
        /// </summary>
        protected void iniPlugIns()
        {
            if (myRecordProp.PlugIns==null)
            {
                return;
            }
            foreach (string plugin in myRecordProp.PlugIns)
            {
                if (myParentServer.PlugIns.Contains(plugin) == false)
                {
                    continue;
                }
                Assembly  pluginAsm=(Assembly)(myParentServer.PlugIns[plugin]);
                string TpluginTypeName=(string)(myParentServer.PlugInType[plugin]);
                IJEPlugIn pluginInst = ((IJEPlugIn)pluginAsm.CreateInstance(TpluginTypeName));
                myPlugIns.Add(plugin, pluginInst);
                pluginInst.IniPlugIn(myRecordProp.eXtension,this);
            }
        }

        /// <summary>
        /// Starts the record.start the scan thread.   
        /// If not self scan it will regist the server(interval=0) scan event or the passive scan event (interval=<0)
        /// </summary>
        /// <returns></returns>
        public virtual bool StartRecord()   
        {
            //ini flush
            if (myRecordProp.SCANINTERVAL < 0)    //pasive
            {
                if (myDriver != null)
                {
                    myDriver.OnFlush += new FlushRecordHandler(this.BigFlush);
                }
            }
            else if (myRecordProp.SCANINTERVAL > 0) //self flush
            {
                //initial flush thread here
                FlushDelegate = new TimerCallback(SelfFlush);
                FlushTimer = new Timer(FlushDelegate, null, myRecordProp.SCANINTERVAL, myRecordProp.SCANINTERVAL);
                OnMyFlush += new FlushRecordHandler(this.BigFlush); //other if want to konw when this recor get flushed can also regsit this event
            }
            //if server flush then do nothing
            IsRunning = true;
            return IsRunning;
        }



        /// <summary>
        /// Initialize the record driver.
        /// <para>ini the driver for this record usually ini a certain channel</para>
        /// </summary>
        /// <returns></returns>
        protected  bool IniRecordDriver()   //ini the driver for this record usually ini a certain channel
        {

            if (myDriver != null)
            {
                try
                {
                    myDriver.IniChannel(myRecordProp);
                    return true;
                }
                catch
                {
                    return false;   //ini driver failed
                }
            }
            else
            {
                return true;
            }
            

        }

        #endregion

        #region properties nearly all in the Interface

        /// <summary>
        /// Gets a value indicating whether this <see cref="JEpicsRecord"/> is running.
        /// </summary>
        /// <value>
        ///   <c>true</c> if running; otherwise, <c>false</c>.
        /// </value>
        public bool Running
        {
            get { return IsRunning; }
        }


        /// <summary>
        /// Gets the parent server.
        /// You can access the server by using this interface .and then access other record on this server
        /// </summary>
        public virtual IJEServer ParentServer
        {
            get 
            {
                return (IJEServer)myParentServer;
            }
        }

        /// <summary>
        /// Gets the record property.
        /// Contains all informations that the record has
        /// <seealso cref="T:JtextEpicsInterface.Record_prop"/>
        /// </summary>
        public virtual Record_prop RecordProperty
        {
            get
            {
                return myRecordProp;
            }
        }

        /// <summary>
        /// Gets the name of the pv.
        /// </summary>
        /// <value>
        /// The name of the pv.
        /// </value>
        public virtual string pvName
        {
            get
            {
                return MyPvName;
            }
        }

        /// <summary>
        /// Gets or sets the VAL.
        /// <para>this val is not the pv value,it the val the is not been proceessed by Pluguin</para>
        /// 	<para>For output this is equals to PV value ,youcan change this to meet the hardware output</para>
        /// 	<para>For input if the raw value from the hardware,you can this it will be put into PV later</para>
        /// </summary>
        /// <value>
        /// The VAL.
        /// </value>
        public virtual object VAL
        {
            get
            {
                return ValB;
            }
            set
            {
                ValB = value;
            }
        }

        /// <summary>
        /// Gets or sets the PV val,not if it a input record it will override this PV value by VAL.
        /// </summary>
        /// <value>
        /// The PVAL.
        /// </value>
        public virtual  object  PVAL
        {
            get
            {
                return ValB ;
            }
            set
            {
                ValB  =value;
            }
        }

       #endregion


        public virtual  void Dispose()
        {
  
        }


    }
}
